package com.base.dao;


import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.base.exceptions.DaoException;

@Repository(value="genericDao")
public abstract class GenericDaoImpl<T extends Serializable, KeyType extends Serializable> 
		implements GenericDao<T, KeyType> {

	private static final long serialVersionUID = 1L;
	
	@Autowired
	private SessionFactory factory; 	

	private Class<T> entityClass;
	
    @SuppressWarnings("unchecked")
	public GenericDaoImpl() {
        this.entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }
    
    public SessionFactory getSessionFactory() {
    	return factory;
    }
    
	public Class<T> getEntityClass() {
		return entityClass;
	}

	/*
	 * Metodos de ABM
	 */
	@Transactional
	public void add(T entity) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		session.saveOrUpdate(entity);
		session.flush();
		session.evict(entity);
	}

	@Transactional
	public void merge(T entity) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		session.merge(entity);
		session.flush();
		session.evict(entity);
	}

	public void set(T entity) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		session.update(entity);
		session.flush();
		session.evict(entity);
	}

	public void del(T entity) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		session.delete(entity);
		session.flush();
		session.evict(entity);
	}
	
	public void deleteAll() {
		Session session = getSessionFactory().getCurrentSession();
        String hqlDelete = "delete " + getEntityClass().getName();
        session.createQuery(hqlDelete).executeUpdate();
	}

	public void deleteQuery(String condition) {
		Session session = getSessionFactory().getCurrentSession();
        String hqlDelete = "delete " + getEntityClass().getName() + " where "+condition;
        session.createQuery(hqlDelete).executeUpdate();
	}
	
	public void deleteById(KeyType id) throws DaoException {
		T entity = get(id);
	    del(entity);
	}

	/*
	 * Metodos de Consulta
	 */
	public long count() {
		Session session = getSessionFactory().getCurrentSession();
		Criteria criteria = session.createCriteria(getEntityClass());
		criteria.setProjection(Projections.rowCount());
        return ((Long) criteria.uniqueResult()).longValue();
	}

	@SuppressWarnings("unchecked")
	public T get(KeyType id) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		return (T) session.get(getEntityClass(), id);
	}

	public T getByCodigo(Object codigo) throws DaoException {
		return getUnique("codigo", codigo);
	}

	public T getUnique(String campoUnique, Object valorUnique) throws DaoException {
		Map<String, Object> filtros = new HashMap<String, Object>();

		filtros.put(campoUnique, valorUnique);
		
		List<T> list = getByQuery(filtros);
		
		T returnObject = null; 
		if (list!=null){
			if (list.size()>0){
				returnObject = (T) list.get(0);
			}
		}
		 
		return returnObject;
	}

	public List<T> getAll() throws DaoException {
		return getAll(0, 0);
	}

	public List<T> getByQuery(T entity) throws DaoException {
		return getByQuery(entity, 0, 0);
	}

	public List<T> getByCriteria(Criterion... criterion) {
		return getByCriteria(0, 0, criterion);
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll(int firstRow, int maxResult) throws DaoException {
		Session session = getSessionFactory().getCurrentSession();
		Criteria criteria = session.createCriteria(getEntityClass());
		
		if (maxResult > 0) {
			criteria.setFirstResult(firstRow);
			criteria.setMaxResults(maxResult);
		}
		
		criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
		
		return criteria.list();
	}

	public List<T> getByQuery(Map<String, Object> filtros, int firstRow, int maxResult) throws DaoException {
		filtros.put("first:", firstRow);
		filtros.put("max:", maxResult);

		return getByQuery(filtros);
	}

	@SuppressWarnings("unchecked")
	public List<T> getByQuery(T entity, int firstRow, int maxResult) throws DaoException {
		Criteria criteria = getSessionFactory().getCurrentSession().createCriteria(getEntityClass());
		Example hibExample = Example.create(entity);
		
		criteria.add(hibExample);
		if (maxResult > 0) {
			criteria.setFirstResult(firstRow);
			criteria.setMaxResults(maxResult);
		}

		return criteria.list();
	}

	@SuppressWarnings("unchecked")
	public List<T> getByCriteria(int firstRow, int maxResult, Criterion... criterion) {
		Session session = getSessionFactory().getCurrentSession();
        Criteria criteria = session.createCriteria(getEntityClass());
        
        for (Criterion c : criterion) {
        	criteria.add(c);
        }

		if (maxResult > 0) {
			criteria.setFirstResult(firstRow);
			criteria.setMaxResults(maxResult);
		}
		
		return criteria.list();
	}


	@Override
	public T getByQueryUnique(T entity) throws DaoException {
		T value = null;
		
		List<T> entities = getByQuery(entity);
		
		if(entities.size() > 0 )
			value = entities.get(0);
			
		return value;
	}

	@Override
	public List<T> getByConsulta(Map<String, Object> filtros, Integer firstResult, Integer maxResults, 
									String sortField, String sortOrder, Object count) {
		return null;
	}

	@SuppressWarnings("unchecked")
	public List<T> getByQuery(Map<String, Object> filtros) {
		Session session = getSessionFactory().getCurrentSession();
		Criteria criteria = session.createCriteria(getEntityClass());
		
		setFiltros(filtros, criteria);

		return criteria.list();	
	}

	protected void setFiltros(Map<String, Object> filtros, Criteria criteria) {
		if (filtros != null){
			Iterator<String> it = filtros.keySet().iterator();

			while (it.hasNext()){
				String propertyName = it.next();
				
				Object value = filtros.get(propertyName);
				
			    if (value != null) {
					if (propertyName.contains("first:")){
						criteria.setFirstResult((Integer) value);
					} else if (propertyName.contains("max:")){
						criteria.setMaxResults((Integer) value);
					} else if (propertyName.contains("like:")){
						propertyName = propertyName.substring(5);
						criteria.add(Restrictions.like(propertyName, value));
					}else if (propertyName.contains("ge:")){
						propertyName = propertyName.substring(3);
						criteria.add(Restrictions.ge(propertyName, value));
					}else if (propertyName.contains("gt:")){
						propertyName = propertyName.substring(3);
						criteria.add(Restrictions.gt(propertyName, value));
					}else if (propertyName.contains("le:")){
						propertyName = propertyName.substring(3);
						criteria.add(Restrictions.le(propertyName, value));
					}else if (propertyName.contains("lt:")){
						propertyName = propertyName.substring(3);
						criteria.add(Restrictions.lt(propertyName, value));
					} else if (propertyName.contains("between:")) {
						propertyName = propertyName.substring(8);
						Object [] params = (Object []) value;
						criteria.add(Restrictions.between(propertyName, params[0], params[1]));
					} else if (propertyName.contains("orderBy:")) {
						propertyName = propertyName.substring(8);
						if (propertyName.equalsIgnoreCase("asc")) {
							criteria.addOrder(Order.asc(value.toString()));
						} else if (propertyName.equalsIgnoreCase("desc")) {
							criteria.addOrder(Order.desc(value.toString()));							
						}
					} else if (propertyName.equals("fn:max")) {
						criteria.setProjection( Projections.max(value.toString()) );
					} else{
						criteria.add(Restrictions.eq(propertyName, value));
					}
			    }
			}
		}
	}
}
